package top.z.template.parse;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.z.template.Error;
import top.z.template.Source;

import java.util.ArrayList;
import java.util.List;

public class Tokenizer {

    private final static String TAG_START = "<%";
    private final static String TAG_CLOSE = "%>";

    private final static Logger logger = LoggerFactory.getLogger(Tokenizer.class);
    private final static Gson   gson   = new GsonBuilder()
            .serializeNulls()
            .addSerializationExclusionStrategy(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes f) {
                    return f.getName().equals("source");
                }

                @Override
                public boolean shouldSkipClass(Class<?> clazz) {
                    return false;
                }
            })
            .create();

    /**
     * 将源代码通过{@link TokenType}标记为{@link Token}, 未包裹在{@link Tokenizer#TAG_START}和{@link Tokenizer#TAG_CLOSE}中的文本块将被处理为{@link TokenType#Text}类型, {{and}}不会被作为独立的标记返回
     */
    public List<Token> tokenize(Source source) {
        List<Token> tokenList = new ArrayList<>();
        if (source.getContent() == null || source.getContent().isEmpty()) {
            return tokenList;
        }
        CharacterStream stream = new CharacterStream(source);

        while (stream.hasNext()) {
            // 匹配指令开始标识, 如果没有匹配到, 把字符串流中的游标加一, 匹配到的话, 把指令之前的内容处理为Text
            if (stream.match(TAG_START, false)) {
                // 不为空
                if (stream.notEmpty()) {
                    // 添加游标之前的内容为TokenType.Text
                    tokenList.add(new Token(TokenType.Text, stream.slice()));
                }
                // 移动字符串流游标
                stream.start();
                // 匹配指令结束标识
                while (!stream.match(TAG_CLOSE, true)) {
                    if (!stream.hasNext()) {
                        throw new RuntimeException("Did not find closing " + TAG_CLOSE + ".");
                    }
                    stream.consume();
                }
                // 这里才是正式开始匹配指令内容
                tokenList.addAll(tokenizeCodeSpan(stream.slice()));
                stream.start();
            } else {
                stream.consume();
            }
        }
        if (stream.notEmpty()) {
            tokenList.add(new Token(TokenType.Text, stream.slice()));
        }
        return tokenList;
    }

    private static List<Token> tokenizeCodeSpan(Span span) {
        Source          source    = span.getSource();
        List<Token>     tokenList = new ArrayList<>();
        CharacterStream stream    = new CharacterStream(source, span.getStart(), span.getEnd());
        // 匹配并且跳过指令开始标识
        if (!stream.match(TAG_START, true)) {
            // 不是开始标签, 抛出异常
            throw Error.error("Expected " + TAG_START, new Span(source, stream.getIndex(), stream.getIndex() + 1));
        }

        outer:
        while (stream.hasNext()) {
            // 跳过连续的空白字符
            stream.skipWhiteSpace();

            // 匹配数字, 包括文本数字, 整数和浮点数. 数字文本可以用类型标识符作为后缀
            if (stream.matchDigit(false)) {
                TokenType type = TokenType.Integer;
                stream.start();
                // 消费字符流, 直到下一个匹配项不是数字为止
                while (true) {
                    if (!stream.matchDigit(true)) {
                        break;
                    }
                }
                // 匹配是否有点, 有的话按照浮点类型处理
                if (stream.match(TokenType.Dot.key(), true)) {
                    type = TokenType.Float;
                    while (true) {
                        if (!stream.matchDigit(true)) {
                            break;
                        }
                    }
                }

                if (stream.match("b", true)) { // Byte
                    if (type == TokenType.Float) {
                        throw Error.error("Byte literal can not have a decimal point.", stream.slice());
                    }
                    type = TokenType.Byte;
                } else if (stream.match("s", true)) { // Short
                    if (type == TokenType.Float) {
                        throw Error.error("Short literal can not have a decimal point.", stream.slice());
                    }
                    type = TokenType.Short;
                } else if (stream.match("l", true)) { // Long
                    if (type == TokenType.Float) {
                        throw Error.error("Long literal can not have a decimal point.", stream.slice());
                    }
                    type = TokenType.Long;
                } else if (stream.match("f", true)) { // Float
                    type = TokenType.Float;
                } else if (stream.match("d", true)) { // Double
                    type = TokenType.Double;
                }
                tokenList.add(new Token(type, stream.slice()));
                continue;
            }

            // Character
            if (stream.match(TokenType.singleQuote.key(), false)) {
                stream.start();
                stream.consume();
                // 跳过转义
                stream.match("\\", true);
                stream.consume();
                if (!stream.match(TokenType.singleQuote.key(), true)) {
                    throw Error.error("Expected closing " + TokenType.singleQuote.key() + " for character literal.", stream.slice());
                }
                tokenList.add(new Token(TokenType.Character, stream.slice()));
                continue;
            }

            // 以双引号开头的 String
            if (stream.match(TokenType.DoubleQuote.key(), true)) {
                stream.start();
                boolean matchedEndQuote = false;
                while (stream.hasNext()) {
                    // 跳过转义
                    if (stream.match("\\", true)) {
                        stream.consume();
                    }
                    // 如果匹配到结束双引号, 则跳出循环
                    if (stream.match(TokenType.DoubleQuote.key(), true)) {
                        matchedEndQuote = true;
                        break;
                    }
                    stream.consume();
                }
                if (!matchedEndQuote) {
                    throw Error.error("String literal is not closed by double quote", stream.slice());
                }
                Span stringSpan = stream.slice();
                stringSpan = new Span(stringSpan.getSource(), stringSpan.getStart() - 1, stringSpan.getEnd());
                tokenList.add(new Token(TokenType.String, stringSpan));
                continue;
            }

            // 以反引号开头的 String
            if (stream.match(TokenType.Backtick.key(), true)) {
                stream.start();
                boolean matchedEndQuote = false;
                while (stream.hasNext()) {
                    if (stream.match("\\", true)) {
                        stream.consume();
                    }
                    if (stream.match(TokenType.Backtick.key(), true)) {
                        matchedEndQuote = true;
                        break;
                    }
                    stream.consume();
                }
                if (!matchedEndQuote) {
                    throw Error.error("String literal is not closed by double quote", stream.slice());
                }
                Span stringSpan = stream.slice();
                stringSpan = new Span(stringSpan.getSource(), stringSpan.getStart() - 1, stringSpan.getEnd());
                tokenList.add(new Token(TokenType.String, stringSpan));
                continue;
            }

            // Identifier, keyword, boolean, or null
            if (stream.matchIdentifierStart(true)) {
                stream.start();
                while (true) {
                    if (!stream.matchIdentifierPart(true)) {
                        break;
                    }
                }

                Span identifierSpan = stream.slice();
                identifierSpan = new Span(identifierSpan.getSource(), identifierSpan.getStart() - 1, identifierSpan.getEnd());

                if (identifierSpan.getText().equals("null")) {
                    tokenList.add(new Token(TokenType.Null, identifierSpan));
                } else if (identifierSpan.getText().equals("true") || identifierSpan.getText().equals("false")) {
                    tokenList.add(new Token(TokenType.Boolean, identifierSpan));
                } else {
                    tokenList.add(new Token(TokenType.Identifier, identifierSpan));
                }
                continue;
            }

            // Simple token
            for (TokenType type : TokenType.valueList()) {
                // 这里还需要调过TAG_CLOSE, 先匹配TAG_CLOSE的内容是否包含TokenType的内容
                if (TAG_CLOSE.length() >= type.key().length() && TAG_CLOSE.contains(type.key())) {
                    if (stream.match(TAG_CLOSE, false)) {
                        // 匹配到结束标签, 跳过Simple token
                        break;
                    }
                }
                if (stream.match(type.key(), true)) {
                    tokenList.add(new Token(type, new Span(source, stream.getIndex() - type.key().length(), stream.getIndex())));
                    continue outer;
                }
            }

            // 匹配结束标识
            if (stream.match(TAG_CLOSE, false)) {
                break;
            }

            // single right curly for object literals
            if (stream.match("}", false)) {
                stream.start();
                stream.match("}", true);
                tokenList.add(new Token(TokenType.RightCurly, stream.slice()));
                continue;
            }
            throw Error.error("Unknown token", new Span(source, stream.getIndex(), stream.getIndex() + 1));
        }
        if (!stream.match(TAG_CLOSE, true)) {
            throw Error.error("Expected " + TAG_CLOSE, new Span(source, stream.getIndex(), stream.getIndex() + 1));
        }
        return tokenList;
    }
}
